package excel.client;

import excel.talkapi.fromclient.ClientRequest;
import excel.talkapi.fromclient.message.*;
import excel.talkapi.fromserver.ServerResponse;
import excel.talkapi.fromserver.message.ServerStatus;
import excel.client.exception.ExcelException;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.UUID;

/**
 * Excel客户端，使用Socket链接Excel服务器
 */
public class ExcelClient {

    private final SocketAddress sa;
    private final Socket socket;
    private final ObjectInputStream ois;
    private final ObjectOutputStream oos;

    private final int buffSize = 200;

    // 初始化
    public ExcelClient(String host, int port) throws ExcelException {
        // 建立链接并握手，握手内容包括了服务器的状态
        sa = new InetSocketAddress(host, port);
        //开始
        socket = new Socket();
        //建立链接
        try {
            socket.connect(sa, 5000);
        } catch (IOException ex) {
            throw new ExcelException("Excel服务器链接超时", ex);
        }
        //开始对话,HELLO
        try {
            oos = new ObjectOutputStream(socket.getOutputStream());
            ClientRequest clientRequest = new ClientRequest();
            clientRequest.setActions(new ClientMessage[]{Build.HELLO});
            oos.writeObject(clientRequest);
            oos.flush();
        } catch (IOException ex) {
            closeAll();
            throw new ExcelException("Excel服务器不稳定，无法正确建立会话", ex);
        }
        ServerResponse ss;
        try {
            //服务器HELLO的回应
            ois = new ObjectInputStream(socket.getInputStream());
            ss = (ServerResponse) ois.readObject();
        } catch (Throwable e) {
            closeAll();
            throw new ExcelException("Excel服务器不稳定，通信失败", e);
        }
        //服务器是否繁忙
        if (!ss.getStatus().equals(ServerStatus.READY)) {
            closeAll();
            throw new ExcelException("Excel服务器繁忙,使用用户过多.");
        }
    }

    //如果过程发生异常则关闭链接
    private void closeAll() {
        try {
            if (ois != null)
                ois.close();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        try {
            if (oos != null)
                oos.close();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        try {
            if (socket != null)
                socket.close();
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    // 关闭客户端
    public void close() {
        closeAll();
    }

    //检查推送的数据是否成功构建Excel否则抛出异常
    private void checkResponse(ServerResponse response) throws ExcelException {
        if (response.getStatus().equals(ServerStatus.SUCCESS)) {
        } else if (response.getStatus().equals(ServerStatus.EXCEPTION)) {
            closeAll();
            throw new ExcelException("Excel内容错误，请检查Excel生成是否有误.", response.getException());
        } else {
            throw new ExcelException("Excel内容错误，请检查Excel生成是否有误");
        }
    }

    private ArrayList<ClientMessage> messagesBuff = new ArrayList<ClientMessage>();

    private void sendRequest(ClientRequest clientRequest) throws ExcelException {
        ServerResponse response;
        try {
            oos.writeObject(clientRequest);
            oos.flush();
            response = (ServerResponse) ois.readObject();
        } catch (Throwable e) {
            closeAll();
            throw new ExcelException("Excel服务器不稳定，通信失败", e);
        }
        checkResponse(response);
    }

    public void flushBuff() throws ExcelException {
        if (messagesBuff.size() > 0) {
            ClientRequest clientRequest = new ClientRequest();
            clientRequest.setActions(messagesBuff.toArray(new ClientMessage[0]));
            sendRequest(clientRequest);
            messagesBuff.clear();
        }
    }

    //发出一个指令
    private void addMessage(ClientMessage clientMessage) throws ExcelException {
        messagesBuff.add(clientMessage);
        if (messagesBuff.size() >= buffSize) {
            flushBuff();
        }
    }

    public ExcelStyle createCellStyle(short fontColor, short backgroundColor, int excelAlign) throws ExcelException {
        CreateStyle createStyle = new CreateStyle();
        createStyle.setFontColor(fontColor);
        createStyle.setBackgroundColor(backgroundColor);
        createStyle.setHorizontal(excelAlign / 10);
        createStyle.setVertical(excelAlign % 10);
        createStyle.setUuid(UUID.randomUUID().toString());
        addMessage(createStyle);
        ExcelStyle excelStyle = new ExcelStyle();
        excelStyle.setUuid(createStyle.getUuid());
        return excelStyle;
    }

    public ExcelStyle createCellStyle(short fontColor, short backgroundColor) throws ExcelException {
        return createCellStyle(fontColor, backgroundColor, 0);
    }

    private CreateLine makeLine(Object[] objects, ExcelStyle excelStyle) {
        Object[] lineObjects = new Object[objects.length];
        for (int i = 0; i < objects.length; i++) {
            Object object = objects[i];
            if (object == null) {
                lineObjects[i] = null;
            } else {
                if (object instanceof String) {
                    lineObjects[i] = object;
                } else if (object instanceof Number) {
                    lineObjects[i] = object;
                } else if (object instanceof Date) {
                    lineObjects[i] = object;
                } else {
                    lineObjects[i] = object.toString();
                }
            }
        }
        CreateLine createLine = new CreateLine();
        createLine.setObjects(lineObjects);
        if (excelStyle != null) {
            createLine.setCellStyle(excelStyle.getUuid());
        }
        return createLine;
    }

    /**
     * 增加一行
     *
     * @param objects 内容
     */
    public void createLine(Object[] objects, ExcelStyle excelStyle) throws ExcelException {
        addMessage(makeLine(objects, excelStyle));
    }

    public void createLine(Object[] objects) throws ExcelException {
        createLine(objects, null);
    }

    /**
     * 创建一个新的Sheet指令
     *
     * @param name
     * @throws ExcelException
     */
    public void createSheet(String name) throws ExcelException {
        CreateSheet createSheet = new CreateSheet();
        createSheet.setSource(CreateSheet.SheetSource.NEW);
        createSheet.setName(name);
        addMessage(createSheet);
    }

    public void createFreezeTop(int rowCount) throws ExcelException {
        CreateFreezeTop createFreezeTop = new CreateFreezeTop();
        createFreezeTop.setRowCount(rowCount);
        addMessage(createFreezeTop);
    }

    public void createSheet() throws ExcelException {
        createSheet(null);
    }

    private String getDocumentAddress(String sheetName, int row, int col) {
        //row是用行号代替0行行号为1，
        String rowNum = String.valueOf(row + 1);
        String colNumber = "";
        //‘A’=65
        // Excel的列计算中 A为0,1 则Z为0,26 ，但Excel中 Z的下一位AA则为 1,1 而不是 1,0 每次上升1位则会少一个数字，固得出以下算法
        col++;
        do {
            col--;
            colNumber = colNumber + new String(new char[]{(char) (65 + (col % 26))});
            col = col / 26;
        } while (col > 0);
        String address = "#" + sheetName + "!" + colNumber + rowNum;
        return address;
    }

    public void createLinkToOtherCell(int row, int col, String toSheetName, int toRow, int toCol) throws ExcelException {
        CreateLink createLink = new CreateLink();
        createLink.setType(CreateLink.LinkType.DOCUMENT);
        createLink.setRow(row);
        createLink.setCol(col);
        createLink.setAddress(getDocumentAddress(toSheetName, toRow, toCol));
        addMessage(createLink);
    }

    public void createMerge(int firstRow, int lashRow, int firstCol, int lastCol) throws ExcelException {
        CreateMerge createMerge = new CreateMerge();
        createMerge.setFirstRow(firstRow);
        createMerge.setLastRow(lashRow);
        createMerge.setFirstCol(firstCol);
        createMerge.setLastCol(lastCol);
        addMessage(createMerge);
    }

    public void createMerges(ExcelMerge[] excelMerges) throws ExcelException {
        CreateMerge[] createMergesArray = new CreateMerge[excelMerges.length];
        for (int i = 0; i < excelMerges.length; i++) {
            ExcelMerge excelMerge = excelMerges[i];
            CreateMerge createMerge = new CreateMerge();
            createMerge.setFirstRow(excelMerge.getFirstRow());
            createMerge.setLastRow(excelMerge.getLastRow());
            createMerge.setFirstCol(excelMerge.getFirstCol());
            createMerge.setLastCol(excelMerge.getLastCol());
            createMergesArray[i] = createMerge;
        }
        CreateMerges createMerges = new CreateMerges();
        createMerges.setCreateMerges(createMergesArray);
        addMessage(createMerges);
    }

    /**
     * 此方法不可用,如果需要使用则需要服务器在createSheet之后执行sheet.trackAllColumnsForAutoSizing();
     * 此方法的开销是巨大的,固将其禁用
     * <p>
     * ----  牛欢20171115
     * <p>
     * //    * @param firstCol
     * //    * @param lastCol
     *
     * @throws ExcelException
     */
//    public void autoSetColWidth(int firstCol, int lastCol) throws ExcelException {
//        AutoSetWidth autoSetWidth = new AutoSetWidth();
//        autoSetWidth.setStartCol(firstCol);
//        autoSetWidth.setStopCol(lastCol);
//        addMessage(autoSetWidth);
//    }

    /**
     * 设置列宽
     */
    public void setColumnWidth(int columnIndex, int width) throws ExcelException {
        SetColumnWidth setColumnWidth = new SetColumnWidth();
        setColumnWidth.setColumnIndex(columnIndex);
        setColumnWidth.setWidth(width * 2 * 256);
        addMessage(setColumnWidth);
    }

    /**
     * @param simpleDateFormat
     * @throws ExcelException
     */
    public void createDefaultSimpleDateFormat(SimpleDateFormat simpleDateFormat) throws ExcelException {
        CreateDefaultSimpleDateFormat createDefaultSimpleDateFormat = new CreateDefaultSimpleDateFormat();
        createDefaultSimpleDateFormat.setSimpleDateFormat(simpleDateFormat);
        addMessage(createDefaultSimpleDateFormat);
    }

    public InputStream build() throws ExcelException {
        flushBuff();
        addMessage(Build.BUILD);
        flushBuff();
        return ois;
    }
}
